﻿Imports System.Text
Imports System.Windows.Markup
Imports System.IO
Imports System.Windows.Interop

Partial Public Class ucFilter
	Implements System.ComponentModel.INotifyPropertyChanged

	Private _filter As IFilter = Nothing
	Private _pageSize As Integer = -1
	Private _alternating As String = String.Empty
	Private _selected As String = String.Empty
	Private WithEvents _timer As New System.Timers.Timer(500)
	Private Shared _frm As Window

	Public Event Selected(ByVal value As Object)

	Public Property ControlResources() As String
		Get
			Return XamlWriter.Save(Me.Resources)
		End Get
		Set(ByVal value As String)
			ctlList.View = Nothing
			ctlList.ItemTemplate = Nothing
			SetResources(ctlList, value)
		End Set
	End Property
	Public Property Filter() As IFilter
		Get
			Return _filter
		End Get
		Set(ByVal value As IFilter)
			_filter = value
		End Set
	End Property
	Public Property ItemTemplate() As String
		Get
			Return XamlWriter.Save(ctlList.ItemTemplate)
		End Get
		Set(ByVal value As String)
			_pageSize = -1
			ctlList.View = Nothing
			If value = String.Empty Then
				ctlList.ItemTemplate = Nothing
				Exit Property
			End If
			Dim sr As New StringReader(value)
			Dim xr = Xml.XmlReader.Create(sr)
			ctlList.ItemTemplate = XamlReader.Load(xr)
		End Set
	End Property
	Public ReadOnly Property List() As IList
		Get
			Return TryCast(ctlList.ItemsSource, IList)
		End Get
	End Property
	Public Sub SetIndex(ByVal index As Integer)
		ctlList.SelectedIndex = index
	End Sub
	Public ReadOnly Property SelectedItem() As Object
		Get
			Return ctlList.SelectedItem
		End Get
	End Property
	Public Property Text() As String
		Get
			Return ctlFilter.Text
		End Get
		Set(ByVal value As String)
			ctlFilter.Text = value
			ctlFilter.SelectAll()
		End Set
	End Property
	Public Property View() As String
		Get
			Return XamlWriter.Save(ctlList.View)
		End Get
		Set(ByVal value As String)
			_pageSize = -1
			ctlList.ItemTemplate = Nothing
			If value = String.Empty Then
				ctlList.View = Nothing
				Exit Property
			End If
			Dim sr As New StringReader(value)
			Dim xr = Xml.XmlReader.Create(sr)
			ctlList.View = XamlReader.Load(xr)
		End Set
	End Property
	Public Property ViewDefinition() As FilterView
		Get
			Dim fv As New FilterView
			fv.ItemTemplate = ItemTemplate
			fv.View = View
			fv.ResourceDictionary = ControlResources
			Return fv
		End Get
		Set(ByVal value As FilterView)
			If value.ResourceDictionary = String.Empty Then
				Dim sb As New StringBuilder
				sb.AppendLine("<ResourceDictionary xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""")
				sb.AppendLine("	xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">")
				If _selected <> String.Empty Then
					Dim colors() = Split(_selected, "/")
					Call CreateHighlight(sb, "HighlightBrushKey", colors)
					Call CreateHighlight(sb, "ControlBrushKey", colors)
				End If
				sb.AppendLine("	<Style x:Key=""FilterList"" TargetType=""ListViewItem"">")
				sb.AppendLine("		<Setter Property=""HorizontalContentAlignment"" Value=""Stretch"" />")
				sb.AppendLine("		<Setter Property=""IsTabStop"" Value=""False"" />")
				sb.AppendLine("	</Style>")
				sb.AppendLine("</ResourceDictionary>")
				ControlResources = sb.ToString
			Else
				ControlResources = value.ResourceDictionary
			End If
			If value.View <> String.Empty Then
				View = value.View
			Else
				ItemTemplate = value.ItemTemplate
			End If
			Dim dv = CollectionViewSource.GetDefaultView(ctlList.ItemsSource)
			If dv IsNot Nothing Then dv.Refresh()
			'ctlList.Items.Refresh()
		End Set
	End Property

	Public Shadows Function Focus() As Boolean
		_timer.Start()
	End Function
	Public Function GridXAML(ByVal definition As String, Optional ByVal _alternating As String = "", Optional ByVal _selected As String = "") As FilterView
		Dim cols() = Split(Replace(definition, ";", ","), ",")
		Dim def As New ColDef
		Dim view As New StringBuilder
		Dim resource As New StringBuilder
		view.AppendLine("<GridView xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""")
		view.AppendLine("	xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">")

		resource.AppendLine("<ResourceDictionary xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""")
		resource.AppendLine("	xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">")

		resource.AppendLine("	<Style TargetType=""ListViewItem"">")
		resource.AppendLine("		<Setter Property=""HorizontalContentAlignment"" Value=""Stretch"" />")
		resource.AppendLine("		<Setter Property=""IsTabStop"" Value=""False"" />")

		If _selected <> String.Empty OrElse _alternating <> String.Empty Then
			resource.AppendLine(String.Format("		<Style.Triggers>"))
			If _selected <> String.Empty Then
				Dim colors() = Split(_selected, "/")
				resource.AppendLine(String.Format("			<Trigger Property=""IsSelected"" Value=""True"">"))
				resource.AppendLine(String.Format("				<Setter Property=""Background"">"))
				resource.AppendLine(String.Format("					<Setter.Value>"))
				If colors.Length = 2 Then
					resource.AppendLine(String.Format("						<LinearGradientBrush EndPoint=""0,1"" StartPoint=""0,0"">"))
					resource.AppendLine(String.Format("							<GradientStop Color=""{0}"" Offset=""0.0"" />", colors(0)))
					resource.AppendLine(String.Format("							<GradientStop Color=""{0}"" Offset=""1.0"" />", colors(1)))
					resource.AppendLine(String.Format("						</LinearGradientBrush>"))
				Else
					resource.AppendLine(String.Format("						<SolidColorBrush Color=""{0}"" />", colors(0)))
				End If
				resource.AppendLine(String.Format("					</Setter.Value>"))
				resource.AppendLine(String.Format("				</Setter>"))
				resource.AppendLine(String.Format("			</Trigger>"))
			End If
			If _alternating <> String.Empty Then
				Dim parts() = Split(_alternating, ";")
				For t = 0 To parts.Length - 1
					Dim colors() = Split(parts(t), "/")
					Call CreateBackgroundTrigger(resource, colors, t)
				Next
				ctlList.AlternationCount = Math.Max(2, parts.Length)
			Else
				ctlList.AlternationCount = 0
			End If
			resource.AppendLine(String.Format("		</Style.Triggers>"))
		End If
		resource.AppendLine("	</Style>")


		For Each col In cols
			If Mid(col, 1, 1) = "-" Then
				' Ignore borders
			Else
				def.Def = col
				Dim align = "Left"
				If def.Align = String.Empty Then
					align = "Left"
				ElseIf InStr(def.Align, "Right", CompareMethod.Text) > 0 Then
					align = "Right"
				Else
					align = "Center"
				End If

				If def.Align <> String.Empty OrElse def.Bold <> String.Empty OrElse def.Italic <> String.Empty OrElse def.Color <> String.Empty OrElse def.Width <> String.Empty OrElse def.Margin <> String.Empty Then
					resource.AppendLine(String.Format("	<DataTemplate x:Key=""{0}Template"">", def.Name))
					resource.Append(String.Format("		<TextBlock Text=""{{Binding {0}}}"" ", def.Name))
					resource.Append(def.Align)
					resource.Append(def.Bold)
					resource.Append(def.Italic)
					resource.Append(def.Color)
					resource.Append(def.Width)
					resource.Append(def.Margin)
					resource.AppendLine("/>")
					resource.AppendLine("	</DataTemplate>")

					view.AppendLine(String.Format("	<GridViewColumn CellTemplate=""{{DynamicResource {0}Template}}"">", def.Name))
					view.AppendLine(String.Format("		<GridViewColumn.Header>"))
					view.AppendLine(String.Format("			<GridViewColumnHeader Content=""{0}"" HorizontalContentAlignment=""{1}"" />", def.Prompt, align))
					view.AppendLine(String.Format("		</GridViewColumn.Header>"))
					view.AppendLine(String.Format("	</GridViewColumn>"))
				Else
					view.AppendLine(String.Format("	<GridViewColumn DisplayMemberBinding=""{{Binding {0}}}"">", def.Name))
					view.AppendLine(String.Format("		<GridViewColumn.Header>"))
					view.AppendLine(String.Format("			<GridViewColumnHeader Content=""{0}"" HorizontalContentAlignment=""{1}"" />", def.Prompt, align))
					view.AppendLine(String.Format("		</GridViewColumn.Header>"))
					view.AppendLine(String.Format("	</GridViewColumn>"))
				End If
			End If
		Next

		resource.AppendLine("</ResourceDictionary>")

		view.AppendLine("</GridView>")

		Return New FilterView With {.View = view.ToString, .ResourceDictionary = resource.ToString}
	End Function
	Public Function ListXAML(ByVal definition As String, Optional ByVal _alternating As String = "", Optional ByVal _selected As String = "") As FilterView
		Dim lines() = Split(definition, ";")
		Dim def As New ColDef
		Dim itemTemplate As New StringBuilder
		Dim resource As New StringBuilder

		itemTemplate.AppendLine("<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""")
		itemTemplate.AppendLine("	xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">")
		itemTemplate.AppendLine("	<StackPanel>")

		If _alternating <> String.Empty Then
			Dim parts() = Split(_alternating, ";")
			ctlList.AlternationCount = Math.Max(2, parts.Length)
			resource.AppendLine("<ResourceDictionary xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""")
			resource.AppendLine("	xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">")
			If _selected <> String.Empty Then
				Dim colors() = Split(_selected, "/")
				Call CreateHighlight(resource, "HighlightBrushKey", colors)
				Call CreateHighlight(resource, "ControlBrushKey", colors)
			End If
			resource.AppendLine("	<Style x:Key=""FilterList"" TargetType=""{x:Type ListViewItem}"">")
			resource.AppendLine("		<Setter Property=""HorizontalContentAlignment"" Value=""Stretch"" />")
			resource.AppendLine("		<Setter Property=""IsTabStop"" Value=""False"" />")
			resource.AppendLine("		<Style.Triggers>")

			For t = 0 To parts.Length - 1
				Dim colors() = Split(parts(t), "/")
				Call CreateBackgroundTrigger(resource, colors, t)
			Next
			resource.AppendLine("		</Style.Triggers>")
			resource.AppendLine("	</Style>")
			resource.AppendLine("</ResourceDictionary>")
		Else
			ctlList.AlternationCount = 0
		End If

		For Each item In lines
			Dim cols() = Split(item, ",")
			If cols.Length = 1 Then
				If Mid(cols(0), 1, 1) = "-" Then
					Dim border = New BorderDef
					border.Def = Mid(cols(0), 2)
					itemTemplate.Append("		<Border ")
					itemTemplate.Append(border.Color)
					itemTemplate.Append(border.Thickness)
					itemTemplate.Append(border.Margin)
					itemTemplate.AppendLine("/>")
					Continue For
				End If
				def.Def = cols(0)
				If def.IncludePrompt Then
					itemTemplate.AppendLine("		<Grid>")
					itemTemplate.AppendLine("			<Grid.ColumnDefinitions>")
					itemTemplate.AppendLine("				<ColumnDefinition Width=""1*""/>")
					itemTemplate.AppendLine("				<ColumnDefinition Width=""2*""/>")
					itemTemplate.AppendLine("			</Grid.ColumnDefinitions>")
					itemTemplate.AppendLine(String.Format("			<TextBlock HorizontalContentAlignment=""Right"" Grid.Column=""0"" Text=""{0}:"" Margin=""0,0,4,0"" />", def.Prompt))
				End If
				itemTemplate.Append("		<TextBlock Text=""{Binding ")
				itemTemplate.Append(def.Name)
				itemTemplate.Append("}"" ")
				If def.IncludePrompt Then
					itemTemplate.AppendLine("Grid.Column=""1"" ")
				End If
				itemTemplate.Append(def.Bold)
				itemTemplate.Append(def.Italic)
				'sb.Append(def.Width)
				itemTemplate.Append(def.Align)
				itemTemplate.Append(def.Color)
				itemTemplate.Append(def.Margin)
				itemTemplate.AppendLine("/>")
				If def.IncludePrompt Then
					itemTemplate.AppendLine("		</Grid>")
				End If
			Else
				itemTemplate.AppendLine("		<Grid>")
				itemTemplate.AppendLine("			<Grid.ColumnDefinitions>")
				For i = 0 To cols.Length - 1
					def.Def = cols(i)
					itemTemplate.Append("				<ColumnDefinition ")
					itemTemplate.Append(def.Width)
					itemTemplate.AppendLine("/>")
				Next
				itemTemplate.AppendLine("			</Grid.ColumnDefinitions>")
				itemTemplate.AppendLine("			<Grid.RowDefinitions>")
				itemTemplate.AppendLine("				<RowDefinition/>")
				itemTemplate.AppendLine("			</Grid.RowDefinitions>")
				For i = 0 To cols.Length - 1
					def.Def = cols(i)
					If def.IncludePrompt Then
						itemTemplate.AppendLine(String.Format("<StackPanel {0}Orientation=""Horizontal"" Grid.Column=""" & i & """>", def.Align))
						itemTemplate.AppendLine(String.Format("	<TextBlock Text=""{0}:"" Margin=""0,0,4,0""/>", def.Prompt))
						itemTemplate.Append("			<TextBlock Text=""{Binding ")
					Else
						itemTemplate.Append("			<TextBlock Grid.Column=""" & i & """ Text=""{Binding ")
					End If
					itemTemplate.Append(def.Name)
					itemTemplate.Append("}"" ")
					itemTemplate.Append(def.Bold)
					itemTemplate.Append(def.Italic)
					itemTemplate.Append(def.Align)
					itemTemplate.Append(def.Color)
					itemTemplate.Append(def.Margin)
					itemTemplate.AppendLine("/>")
					If def.IncludePrompt Then
						itemTemplate.AppendLine("</StackPanel>")
					End If
				Next
				itemTemplate.AppendLine("		</Grid>")
			End If
		Next
		itemTemplate.AppendLine("	</StackPanel>")
		itemTemplate.AppendLine("</DataTemplate>")
		Return New FilterView With {.ItemTemplate = itemTemplate.ToString, .ResourceDictionary = resource.ToString}
	End Function
	Public Sub Move(ByVal direction As Integer)
		FilterMove(direction)
	End Sub
	Public Sub SelectAll()
		ctlFilter.SelectAll()
	End Sub
	Public Sub UpdateList(Optional ByVal preserveLocation As Boolean = False)
		If _filter IsNot Nothing Then
			Dim index = ctlList.SelectedIndex
			Dim list = _filter.GetList(ctlFilter.Text)
			ctlList.ItemsSource = list
			If ctlList.Items.Count > 0 Then
				If preserveLocation Then
					index = Math.Min(Math.Max(0, index), ctlList.Items.Count - 1)
				Else
					index = 0
				End If
				ctlList.SelectedIndex = index
				ctlList.ScrollIntoView(ctlList.SelectedItem)
			Else
				ctlList.SelectedIndex = -1
			End If
			RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("SelectedItem"))
		End If
	End Sub

	Private Sub ctlFilter_GotFocus(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles ctlFilter.GotFocus
		_timer.Stop()
		'ctlFilter.SelectAll()
	End Sub
	Private Sub ucFilter_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
		Call ctlFilter_TextChanged(Nothing, Nothing)
		_timer.Interval = 100
		AddHandler _timer.Elapsed, AddressOf TimeToFocus
	End Sub
	Private Sub ctlFilter_MouseDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles ctlFilter.MouseDoubleClick
		RaiseEvent Selected(ctlList.SelectedItem)
	End Sub
	Private Sub ctlFilter_PreviewKeyDown(ByVal sender As Object, ByVal e As System.Windows.Input.KeyEventArgs) Handles ctlFilter.PreviewKeyDown
		If _pageSize = -1 Then
			_pageSize = Fix(ctlList.RenderSize.Height / ItemHeight()) - 1
		End If
		Select Case e.Key
			Case Key.Enter : RaiseEvent Selected(ctlList.SelectedItem)
			Case Key.Down : FilterMove(1)
			Case Key.Up : FilterMove(-1)
			Case Key.PageDown : FilterMove(_pageSize)
			Case Key.PageUp : FilterMove(-_pageSize)
			Case Key.Home
				If Keyboard.Modifiers = ModifierKeys.Control Then
					FilterMove(-ctlList.SelectedIndex)
				Else
					Exit Sub
				End If
			Case Key.End
				If Keyboard.Modifiers = ModifierKeys.Control Then
					FilterMove(ctlList.Items.Count)
				Else
					Exit Sub
				End If
			Case Else
				Exit Sub
		End Select
		e.Handled = True
	End Sub
	Private Sub ctlFilter_TextChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.TextChangedEventArgs) Handles ctlFilter.TextChanged
		Call UpdateList()
	End Sub

	Private Sub ctlList_GotFocus(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles ctlList.GotFocus
		_timer.Start()
	End Sub
	Private Sub ctlList_MouseDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles ctlList.MouseDoubleClick
		_timer.Start()
		RaiseEvent Selected(ctlList.SelectedItem)
	End Sub
	'Private Sub ucFilter_GotFocus(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.GotFocus
	'	_timer.Start()
	'End Sub

	Private Sub CreateBackgroundTrigger(ByVal resource As StringBuilder, ByVal colors() As String, ByVal index As Integer)
		resource.AppendLine(String.Format("			<Trigger Property=""ItemsControl.AlternationIndex"" Value=""{0}"">", index))
		resource.AppendLine(String.Format("				<Setter Property=""Background"">"))
		resource.AppendLine(String.Format("					<Setter.Value>"))
		If colors.Length = 2 Then
			resource.AppendLine(String.Format("					<LinearGradientBrush EndPoint=""0,1"" StartPoint=""0,0"">"))
			resource.AppendLine(String.Format("						<GradientStop Color=""{0}"" Offset=""0.0"" />", colors(0)))
			resource.AppendLine(String.Format("						<GradientStop Color=""{0}"" Offset=""1.0"" />", colors(1)))
			resource.AppendLine(String.Format("					</LinearGradientBrush>"))
		Else
			resource.AppendLine(String.Format("					<SolidColorBrush Color=""{0}"" />", colors(0)))
		End If
		resource.AppendLine(String.Format("					</Setter.Value>"))
		resource.AppendLine(String.Format("				</Setter>"))
		resource.AppendLine(String.Format("			</Trigger>"))
	End Sub
	Private Sub CreateHighlight(ByVal sb As StringBuilder, ByVal typeName As String, ByVal colors As String())
		If colors.Length = 2 Then
			sb.AppendLine(String.Format("					<LinearGradientBrush x:Key=""{{x:Static SystemColors.{0}}}"" EndPoint=""0,1"" StartPoint=""0,0"">", typeName))
			sb.AppendLine(String.Format("						<GradientStop Color=""{0}"" Offset=""0.0"" />", colors(0)))
			sb.AppendLine(String.Format("						<GradientStop Color=""{0}"" Offset=""1.0"" />", colors(1)))
			sb.AppendLine(String.Format("					</LinearGradientBrush>"))
		Else
			sb.AppendLine(String.Format("	<SolidColorBrush x:Key=""{{x:Static SystemColors.{1}}}"" Color=""{0}"" />", colors(0), typeName))
		End If

	End Sub
	Private Sub DoFocus()
		ctlFilter.Focus()
		Keyboard.Focus(ctlFilter)
	End Sub
	Private Sub FilterMove(ByVal direction As Integer)
		If ctlList.Items.Count = 0 Then Exit Sub
		If ctlList.SelectedIndex < 0 Then ctlList.SelectedIndex = 0
		Dim i = ctlList.SelectedIndex + direction
		i = Math.Max(0, Math.Min(ctlList.Items.Count - 1, i))
		'ctlList.UpdateLayout()
		ctlList.SelectedIndex = i
		ctlList.ScrollIntoView(ctlList.SelectedItem)
	End Sub
	Private Function FindVisualChild(Of childItem As DependencyObject)(ByVal obj As DependencyObject) As childItem
		For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(obj) - 1
			Dim child As DependencyObject = VisualTreeHelper.GetChild(obj, i)
			If child IsNot Nothing AndAlso TypeOf child Is childItem Then
				Return CType(child, childItem)
			Else
				Dim childOfChild As childItem = FindVisualChild(Of childItem)(child)
				If childOfChild IsNot Nothing Then
					Return childOfChild
				End If
			End If
		Next i
		Return Nothing
	End Function
	Private Function ItemHeight() As Double
		If ctlList.SelectedIndex < 0 Then Return 23
		Dim item As ListViewItem = ctlList.ItemContainerGenerator.ContainerFromItem(ctlList.SelectedItem)
		If item Is Nothing Then Return 23
		Return item.RenderSize.Height
	End Function
	'Private Sub SetFocus()
	'	_timer.Start()
	'	'Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.ApplicationIdle, New SimpleDelegate(AddressOf DoFocus))
	'End Sub
	Private Sub TimeToFocus(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs)
		_timer.Stop()
		Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.ApplicationIdle, New SimpleDelegate(AddressOf DoFocus))
		'Call SetFocus()
	End Sub
	Private Delegate Sub SimpleDelegate()
	Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
	Public Sub NotifyChange()
		RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("SelectedItem"))
	End Sub
	Private Sub ctlList_SelectionChanged(ByVal sender As Object, ByVal e As System.Windows.Controls.SelectionChangedEventArgs) Handles ctlList.SelectionChanged
		RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("SelectedItem"))
	End Sub
End Class